﻿#region Header

/*
Remote views based on linq
By

Pascal Hauser 
Dipl. Ing. in Informatik, Hochschule für Technik Rapperswil, 2006
Master Thesis, Hochschule für Technik Rapperswil, 2008-2010

and

Raphael Gfeller
Dipl. Ing. in Informatik, Hochschule für Technik Rapperswil, 2006
Master Thesis, Hochschule für Technik Rapperswil, 2008-2010

*/

#endregion

#region Usings

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using InterLinq;
using InterLinq.Base;
using InterLinq.Services.Interface;
using Sebarf.Core.Internal;
using Sebarf.Diagnostics.Interfaces;
using Sebarf.Services.Interfaces;
using Sebarf.Utils.Interfaces;
using Sebastof.Services.Interfaces;
using Sebastof.Types;
using Utils;

#endregion

namespace Sebastof.Client.Services.Impl {
	/// <summary>
	/// Description of the class
	/// </summary>
	public class ChangeHandlerService : Service, IChangeHandlerService, IChangeHandlerDiagnosticsService,
										IChangeTrackingStorageService {
		#region Public Properties

		[ServiceRequest]
		public IQueryHandlerLocatorService QueryHandlerLocatorService { get; set; }

		[ServiceRequest]
		public IObjectTrackerMetadataService ObjectTrackerMetadataService { get; set; }

		[ServiceRequest]
		public IChangeTrackingCacheService ChangeTrackingCacheService { get; set; }

		[ServiceRequest]
		public IObjectMappingLocatorService ObjectMappingLocatorService { get; set; }

		[ServiceRequest]
		public IRequestRemoteHandlerEngineLocatorService RequestHandlerEngineLocatorService { get; set; }

		[ServiceRequest]
		private IObjectSynchronisationService DatahandlerService { get; set; }

		#endregion

		#region public functions

		public void InsertOnSubmit(object obj) {
			CurrentDepositedObjectsHaveToBeAdded.Add(obj);
		}

		public void DeleteOnSubmit(object obj) {
			CurrentDepositedObjectsShouldDeleted.Add(obj);
		}

		public void Refresh<T>(T obj) where T : class {
			GetType().GetMethod("RefreshInternal").MakeGenericMethod(obj.GetType()).Invoke(this, new object[] { obj });

			//ReloadObjectRequest request = new ReloadObjectRequest();
			//var objInfo = new ReloadObjectInfo();
			//objInfo.type = obja.GetType();
			//objInfo.oid = obja.Id;
			//request.ObjectInfo = objInfo;
			//RequestHandlerEngineLocatorService.GetRemoteEngine().Process(request);
		}

		#region submitchanges related stuff

		private readonly Dictionary<string, List<ChangeSet>> _ChangeSetByObjectId = new Dictionary<string, List<ChangeSet>>();

		private readonly List<ChangeSet> m_ChangesOfNonFullyLoadedObjects = new List<ChangeSet>();
		//Some values of objects are loaded in views, so we don't have the object in the cache

		private readonly List<Object> m_ObjectsToAdd = new List<Object>();
		private readonly List<Object> m_ObjectsToDelete = new List<Object>();
		private readonly List<Object> m_ObjectsWithChanges = new List<Object>();
		private readonly List<Object> m_ObjectsWithCollectionChanges = new List<Object>();

		private Dictionary<Type, List<KeyValuePair<object, List<KeyValuePair<string, object>>>>> m_ChangeTable;
		private Dictionary<Type, Dictionary<object, Dictionary<string, object>>> m_OriginalData;
		private List<object> m_ProcessedObjects;

		#region IChangeHandlerDiagnosticsService Members

		public IList<ChangeSet> GetChangesByNonFullyLoadedObjectById(string oid) {
			return CurrentDepositedChangesOfNonFullyLoadedObjects;
		}

		public IEnumerable<BaseChangetSet> GetAllPendingChanges() {
			var l = new List<BaseChangetSet>();
			foreach (ChangeSet o in GetChangeSetForObjectFields()) {
				l.Add(o);
			}
			foreach (ChangeSet o in GetChangeSetForILAwareObjects()) {
				l.Add(o);
			}
			foreach (DeleteObjectSet o in GetDeleteObjects()) {
				l.Add(o);
			}
			foreach (NewObjectSet o in GetNewObjects()) {
				l.Add(o);
			}
			foreach (ChangeCollectionSet o in GetChangeCollectionSetForILAwareObjects()) {
				l.Add(o);
			}
			return l;
		}

		#endregion

		#region IChangeHandlerService Members

		public void SubmitChanges() {
			//changetable: per type, we have per object identifier a list of changes (field signature, new value)

			try {
				SubmitChangesRequest request = null;
				request = new SubmitChangesRequest {
					ChangesByCreationOfObjects = GetNewObjects(),
					ChangesOnCollection = GetChangeCollectionSetForILAwareObjects()
				};

				var generalChanges = new List<ChangeSet>();
				generalChanges.AddRange(GetChangeSetForObjectFields());
				generalChanges.AddRange(GetChangeSetForILAwareObjects());
				generalChanges.AddRange(CurrentDepositedChangesOfNonFullyLoadedObjects.ToArray());
				request.GeneralChanges = generalChanges;
				request.ChangesByDeletionOfObjects = GetDeleteObjects();

				object res = RequestHandlerEngineLocatorService.GetRemoteEngine().Process(request);

				var resc = (KeyValuePair<string, object>[])res;
				Dictionary<string, WeakReference> lookupTable = ObjectMappingLocatorService.GetIdToObjectMappingTable();
				foreach (var e in resc) {
					//only if we have the object we need update the version, else we don't care.
					if (lookupTable.ContainsKey(e.Key)) {
						WeakReference cacheEntry = lookupTable[e.Key];
						if (cacheEntry.IsAlive) {
							if (cacheEntry.Target is IVersionedObject) {
								((IVersionedObject)cacheEntry.Target).SetVersion(e.Value);
							}
							else {
								throw new NotImplementedException(
									"Currently, a object used by InterLinq has to implement the IVersiondeObject interface, else no version tracking can be made.");
							}
						}
					}
				}

				foreach (object o in CurrentDepositedObjectsHaveChanges) {
					if (o is ISebastofApplicationObject) {
						((ISebastofApplicationObject)o).SignalIsCommited();
					}
				}
				CurrentDepositedObjectsHaveToBeAdded.Clear();
				CurrentDepositedObjectsHaveCollectionChanges.Clear();
				CurrentDepositedObjectsHaveChanges.Clear();
				CurrentDepositedChangesOfNonFullyLoadedObjects.Clear();
				CurrentDepositedObjectsShouldDeleted.Clear();
			}
			catch (Exception err) {
				Check.AssertException(false, err.Message);
			}
		}

		#endregion

		//Dictionary<Type, List<System.Reflection.FieldInfo>> m_fieldInfoLookup;

		#region IChangeTrackingStorageService Members

		public IList<ChangeSet> CurrentDepositedChangesOfNonFullyLoadedObjects {
			get { return m_ChangesOfNonFullyLoadedObjects; }
		}

		public IList<Object> CurrentDepositedObjectsHaveChanges {
			get { return m_ObjectsWithChanges; }
		}

		public IList<Object> CurrentDepositedObjectsHaveCollectionChanges {
			get { return m_ObjectsWithCollectionChanges; }
		}

		public IList<Object> CurrentDepositedObjectsHaveToBeAdded {
			get { return m_ObjectsToAdd; }
		}

		public IList<Object> CurrentDepositedObjectsShouldDeleted {
			get { return m_ObjectsToDelete; }
		}

		#endregion

		public void AddChangeForNotLoadedObject(ChangeSet changeSet) {
			CurrentDepositedChangesOfNonFullyLoadedObjects.Add(changeSet);
			if (!_ChangeSetByObjectId.ContainsKey(changeSet.oid.ToString())) {
				_ChangeSetByObjectId[changeSet.oid.ToString()] = new List<ChangeSet>();
			}
		}

		private void CheckChanges(object o) {
			//ensure that changes of an object are only recorded once

			IObjectHierarchyFlattenerService flattener = ObjectHierarchyFlattenerService;
			List<object> objects = flattener.Flatten(o);
			foreach (object nestedobject in objects) {
				if (m_ProcessedObjects.Contains(nestedobject)) {
					continue;
				}
				if (!(o is IIdentifiable)) {
					continue;
				}
				m_ProcessedObjects.Add(nestedobject);

				Type nestedobjecttype = nestedobject.GetType();

				bool objectchanged = false;

				var ObjectChangeEntry = new KeyValuePair<object, List<KeyValuePair<string, object>>>(nestedobject,
																									  new List
																										<KeyValuePair<string, object>
																										>());

				foreach (FieldInfo field in flattener.FieldInfoLookup[nestedobjecttype]) {
					if (field.FieldType == typeof(EventHandler) || typeof(Delegate).IsAssignableFrom(field.FieldType)) {
						continue;
					}
					object val = field.GetValue(nestedobject);
					if (m_OriginalData.ContainsKey(nestedobjecttype)) {
						object orgval = m_OriginalData[nestedobjecttype][nestedobject][field.Name];
						if ((val == null && orgval != null) || (val != null && !val.Equals(orgval))) {
							ObjectChangeEntry.Value.Add(new KeyValuePair<string, object>(field.Name, val));
							objectchanged = true;
						}
					}
					else {
						ObjectChangeEntry.Value.Add(new KeyValuePair<string, object>(field.Name, val));
						objectchanged = true;
					}
				}

				if (objectchanged) {
					if (!m_ChangeTable.ContainsKey(nestedobjecttype)) {
						m_ChangeTable[nestedobjecttype] = new List<KeyValuePair<object, List<KeyValuePair<string, object>>>>();
					}
					m_ChangeTable[nestedobjecttype].Add(ObjectChangeEntry);
				}
			}
		}

		private List<NewObjectSet> GetNewObjects() {
			var newones = new List<NewObjectSet>();
			IObjectHierarchyFlattenerService flattener = ObjectHierarchyFlattenerService;
			IObjectTrackerMetadataService metadataService = ObjectTrackerMetadataService;
			foreach (object o in CurrentDepositedObjectsHaveToBeAdded) {
				List<object> flattree = flattener.Flatten(o);
				foreach (object flatOcontent in flattree) {
					var oObjc = flatOcontent as ISebastofApplicationObject;
					if (oObjc != null) {
						if (metadataService.GetChangeState(o) == EChangeState.New) {
							var data = new NewObjectSet { oid = oObjc.Id, type = oObjc.GetType() };
							data.interlinqcontent = new List<KeyValuePair<string, object>>();
							data.normalcontent = new List<KeyValuePair<string, object>>();

							//normal field data
							Type flatOcontentType = flatOcontent.GetType();
							foreach (FieldInfo field in flattener.FieldInfoLookup[flatOcontentType]) {
								if (field.FieldType == typeof(EventHandler) || typeof(Delegate).IsAssignableFrom(field.FieldType)) {
									continue;
								}
								object val = field.GetValue(flatOcontent);

								data.normalcontent.Add(new KeyValuePair<string, object>(field.Name, val));
							}

							//interliqn container data
							foreach (var ocontent in oObjc.GetValues()) {
								data.interlinqcontent.Add(new KeyValuePair<string, object>(ocontent.Key, ocontent.Value));
							}

							newones.Add(data);
						}
					}
				}
			}
			return newones;
		}

		public List<DeleteObjectSet> GetDeleteObjects() {
			var ret = new List<DeleteObjectSet>();
			foreach (SebastofApplicationObject o in CurrentDepositedObjectsShouldDeleted) {
				ret.Add(new DeleteObjectSet { type = o.GetType(), oid = o.Id });
			}
			return ret;
		}

		private List<ChangeSet> GetChangeSetForObjectFields() {
			m_ProcessedObjects = new List<object>();
			m_ChangeTable = new Dictionary<Type, List<KeyValuePair<object, List<KeyValuePair<string, object>>>>>();
			m_OriginalData = new Dictionary<Type, Dictionary<object, Dictionary<string, object>>>();

			var changes = new List<ChangeSet>();
			Dictionary<Type, Dictionary<object, Dictionary<string, object>>> EntityStore =
				ChangeTrackingCacheService.GetStoreOfNonSebastofObjectsOriginalData();
			foreach (Type t in EntityStore.Keys) {
				foreach (object o in EntityStore[t].Keys) {
					CheckChanges(o);
				}
			}

			Dictionary<object, string> oidLookup = ObjectMappingLocatorService.GetObjectToIdMappingTable();
			foreach (var t in m_ChangeTable) {
				foreach (var o in t.Value) {
					foreach (var val in o.Value) {
						string id = null;
						if (o.Key is ISebastofApplicationObject) {
							id = (o.Key as ISebastofApplicationObject).Id;
						}
						else {
							if (oidLookup.ContainsKey(o.Key)) {
								id = oidLookup[o.Key];
							}
							else {
								throw new Exception("Can not get Id of Object from MapperService");
							}
						}
						var n = new ChangeSet { oid = id, field = val.Key, newvalue = val.Value };
						changes.Add(n);
					}
				}
			}
			return changes;
		}

		private List<ChangeSet> GetChangeSetForILAwareObjects() {
			var changelistImplSpecific = new List<ChangeSet>();
			foreach (SebastofApplicationObject o in CurrentDepositedObjectsHaveChanges) {
				changelistImplSpecific.AddRange(ObjectTrackerMetadataService.GetChangeSet(o));
			}
			return changelistImplSpecific;
		}

		private List<ChangeCollectionSet> GetChangeCollectionSetForILAwareObjects() {
			var changelistCollectionImplSpecific = new List<ChangeCollectionSet>();
			foreach (SebastofApplicationObject o in CurrentDepositedObjectsHaveCollectionChanges) {
				changelistCollectionImplSpecific.AddRange(ObjectTrackerMetadataService.GetChangeCollectionSet(o));
			}

			return changelistCollectionImplSpecific;
		}

		#endregion

		public void InsertOnSubmit(ISebastofApplicationObject obj) {
			CurrentDepositedObjectsHaveToBeAdded.Add(obj);
		}

		public void DeleteOnSubmit(ISebastofApplicationObject obj) {
			CurrentDepositedObjectsShouldDeleted.Add(obj);
		}

		public void RefreshInternal<T>(T obj) where T : SebastofApplicationObject {
			var obja = (ISebastofApplicationObject)obj;
			IQueryHandler qh = QueryHandlerLocatorService.GetQueryHandler();
			//var qhtype = qh.GetType();
			//var iqo = qhtype.GetMethod("GetTable").MakeGenericMethod(obj.GetType()).Invoke(qh, new object[]{});
			//var iq = (IQueryable<IIdentitity>)iqo;

			// force reload
			DatahandlerService.MarkObjectAsInvalid(obja.Id);

			(from o in qh.GetTable<T>() where o.Id == obja.Id select o).Single();
		}

		#endregion

		#region IChangeHandlerService Members

		public event _ConflictDetectionHandler ConflictDetectionHandler;

		public bool IsThereaNewVersionAvailableForThisObject(object obj) {
			throw new NotImplementedException();
		}

		#endregion
	}
}